// system.h 
//	All global variables used in Nachos are defined here.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#ifndef SYSTEM_H
#define SYSTEM_H

#include "copyright.h"
#include "utility.h"
#include "thread.h"
#include "scheduler.h"
#include "interrupt.h"
#include "stats.h"
#include "timer.h"
#include "synch.h"


// Initialization and cleanup routines
extern void Initialize(int argc, char **argv); 	// Initialization,
						// called before anything else
extern void Cleanup();				// Cleanup, called when
						// Nachos is done.

extern Thread *currentThread;			// the thread holding the CPU
extern Thread *threadToBeDestroyed;  		// the thread that just finished
extern Scheduler *scheduler;			// the ready list
extern Interrupt *interrupt;			// interrupt status
extern Statistics *stats;			// performance metrics
extern Timer *timer;				// the hardware alarm clock
extern int virtualMemType;
extern bool printSpecial;
extern int numPageFaults;
#ifdef USER_PROGRAM
#include "machine.h"
extern Machine* machine;	// user program memory and registers

struct PageTableEntry 
{
	unsigned int inUse;
	Thread* process;
	int virtualPage;
	int place; //marks the entry's spot in the IPT in the order it is added.
};

class InvertedPageTable 
{
	private:
		int pageCounter;
		PageTableEntry *ipt;
		Semaphore *mutex;
		
	public:
		InvertedPageTable()
		{
			pageCounter = 0;
			
			ipt = new PageTableEntry[NumPhysPages];
		    for (int i = 0; i < NumPhysPages; i++) 
		    {
		    	ipt[i].inUse = 0;
		    	ipt[i].process = NULL;
		    	ipt[i].virtualPage = 0;
		    	ipt[i].place = 0;
		    }
		    
		    mutex = new Semaphore("IPT Semaphore", 1);    
		}

		~InvertedPageTable()
		{
			delete mutex;
			delete ipt;
		}

		bool Mark(Thread* p, int virtualAddr)
		{
			mutex->P();
				
			int virtualPage = virtualAddr / PageSize;
			
			for (int i = 0; i < NumPhysPages; i++)
			{
				if (ipt[i].inUse == 0)
				{
					pageCounter++;
				
					ipt[i].inUse = 1;
					ipt[i].process = p;
					ipt[i].virtualPage = virtualPage;
					ipt[i].place = pageCounter;
					ipt[i].process->space->SwapIn(i, virtualAddr);
				
					//Print();
					mutex->V();
					return true;
				}
			}
			
			mutex->V();
			return false;
		}
		
		int Find(Thread* p, int virtualPage)
		{
			for (int i = 0; i < NumPhysPages; i++)
			{
				if (ipt[i].inUse == 1 && ipt[i].process->getThreadID() == p->getThreadID() && ipt[i].virtualPage == virtualPage)
				{
					if (printSpecial)
						printf("Page fault: Process %d requests virtual page %d and is assigned physical page %d.\n", currentThread->getThreadID(), virtualPage, i);			
					return i;
				}
			}
			
			return -1;
		}	
		
		void FIFOSwap(Thread* p, int virtualAddr)
		{
			mutex->P();
			
			int virtualPage = virtualAddr / PageSize;
			
			int oldestIndex = 0;
			
			for (int i = 1; i < NumPhysPages; i++) 
			{
				if (ipt[i].place < ipt[oldestIndex].place) 
				{
					oldestIndex = i;
				}
			}
			
			if (printSpecial)
			{
				printf("\nPage swap required.\n");
				printf("Process IDs: process %d and process %d\n", currentThread->getThreadID(), ipt[oldestIndex].process->getThreadID());
				printf("Requested virtual page %d, is assigned physical page %d.\n", virtualPage, oldestIndex);			
			}

		
			Swap(oldestIndex, p, virtualAddr);
			
			mutex->V();
		}
		
		void RandomSwap(Thread* p, int virtualAddr)
		{
			mutex->P();
			
			int virtualPage = virtualAddr / PageSize;
			int randomIndex;
			randomIndex = Random() % 32;
		
	
			if (printSpecial)
			{
				printf("\nPage swap required.\n");
				printf("Process IDs: process %d and process %d\n", currentThread->getThreadID(), ipt[randomIndex].process->getThreadID());
				printf("Requested virtual page %d, is assigned physical page %d.\n", virtualPage, randomIndex);	
			}

			Swap(randomIndex, p, virtualAddr);

			mutex->V();
		}	
		
		void Swap(int index, Thread* p, int virtualAddr)
		{
			IntStatus oldLevel = interrupt->SetLevel(IntOff);
			
			int virtualPage = virtualAddr / PageSize;
		
			ipt[index].process->space->SwapOut(index, ipt[index].virtualPage);
		
			ipt[index].process = p;
			ipt[index].virtualPage = virtualPage;
			
			pageCounter++;
			ipt[index].place = pageCounter;	
			
			printf("Virtual page %d removed.\n", virtualPage);
			
			ipt[index].process->space->SwapIn(index, virtualAddr);	
			
			
			(void) interrupt->SetLevel(oldLevel);
		}
		
		void Clear(int processID)
		{
			for (int i = 0; i < NumPhysPages; i++)
			{
				if (ipt[i].inUse == 1 && ipt[i].process->getThreadID() == processID)
				{
			    	ipt[i].process = NULL;
			    	ipt[i].virtualPage == -1;
			    	ipt[i].inUse = 0;
				}
			}	
		}

		int NumClear()
		{
			int numClear = 0;
			
			for (int i = 0; i < NumPhysPages; i++)
			{
				if (ipt[i].inUse == 0)
				{
			    	numClear++;
				}
			}
			
			return numClear;
		}

		void Print()
		{
			for (int i = 0; i < NumPhysPages; i++)
			{
				if (ipt[i].inUse == 1)
					printf("%d, ", ipt[i].process->getThreadID());
				else
					printf("-, ");
			}
			
			printf("\n\n");
		}

		int printIPTprocess(int index) {
			return ipt[index].process->getThreadID();
		}

};

extern InvertedPageTable* IPT;

#endif

#ifdef FILESYS_NEEDED 		// FILESYS or FILESYS_STUB 
#include "filesys.h"
extern FileSystem  *fileSystem;
#endif

#ifdef FILESYS
#include "synchdisk.h"
extern SynchDisk   *synchDisk;
#endif

#ifdef NETWORK
#include "post.h"
extern PostOffice* postOffice;
#endif


#endif // SYSTEM_H
